package com.juhuixing.common.assembly.redis;


import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 功能描述: 由于springboot2.x使用Lettuce非jedis，为便于使用顾封装此类
 * @author: 刘猛
 * @date: 2019/4/24 16:06
 **/
@Component
public class RedisServiceUtil {

    public static RedisServiceUtil redisServiceUtil;

    @PostConstruct
    public void init() {
        redisServiceUtil = this;
    }

    @Resource
    public RedisTemplate redisTemplate;
    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public static <T> boolean expire(T key, long time) {
        try {
            if (time > 0) {
                redisServiceUtil.redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public static <T> long getExpire(T key) {
        return redisServiceUtil.redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public static <T> boolean hasKey(T key) {
        try {
            return redisServiceUtil.redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public static <T> void del(T... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisServiceUtil.redisTemplate.delete(key[0]);
            } else {
                redisServiceUtil.redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    // ============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public static <T,K> T get(K key) {
        return key == null ? null : (T)redisServiceUtil.redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public static <K,V> boolean set(K key, V value) {
        try {
            redisServiceUtil.redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public static <K,V> boolean set(K key, V value, long time) {
        try {
            if (time > 0) {
                redisServiceUtil.redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public static <T> long incr(T key, Integer delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisServiceUtil.redisTemplate.opsForValue().increment(key, delta);
    }

    public static <T> Long increment(T key) {
        return redisServiceUtil.redisTemplate.opsForValue().increment(key);
    }

    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public static <T> long decr(T key, Integer delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisServiceUtil.redisTemplate.opsForValue().increment(key, -delta);
    }
    // ================================Map=================================
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public static <K,I,V> V hget(K key, I item) {
        return (V)redisServiceUtil.redisTemplate.opsForHash().get(key, item);
    }
    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public static <K,V,HK> Map<K, V> hmget(HK key) {
        return (Map<K, V>)redisServiceUtil.redisTemplate.opsForHash().entries(key);
    }
    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public static <K,V,HK> boolean hmset(HK key, Map<K, V> map) {
        try {
            redisServiceUtil.redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public static <K,V,HK> boolean hmset(HK key, Map<K, V> map, long time) {
        try {
            redisServiceUtil.redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public static <K,I,V> boolean hset(K key, I item, V value) {
        try {
            redisServiceUtil.redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public static <K,I,V> boolean hset(K key, I item, V value, long time) {
        try {
            redisServiceUtil.redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public static <K,V> void hdel(K key, V... item) {
        redisServiceUtil.redisTemplate.opsForHash().delete(key, item);
    }
    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public static <K,I> boolean hHasKey(K key, I item) {
        return redisServiceUtil.redisTemplate.opsForHash().hasKey(key, item);
    }
    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public static <K,I> double hincr(K key, I item, double by) {
        return redisServiceUtil.redisTemplate.opsForHash().increment(key, item, by);
    }
    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public static <K,I> double hdecr(K key, I item, double by) {
        return redisServiceUtil.redisTemplate.opsForHash().increment(key, item, -by);
    }
    // ============================set=============================
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public static <T,K> Set<T> sGet(K key) {
        try {
            return redisServiceUtil.redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public static <K,V> boolean sHasKey(K key, V value) {
        try {
            return redisServiceUtil.redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public static <K,V> long sSet(K key, V ... values) {
        try {
            return redisServiceUtil.redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public static <K,V> long sSetAndTime(K key, long time, V... values) {
        try {
            Long count = redisServiceUtil.redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public static <K> long sGetSetSize(K key) {
        try {
            return redisServiceUtil.redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public static <K,V> long setRemove(K key, V... values) {
        try {
            Long count = redisServiceUtil.redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================
    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束 0 到 -1代表所有值
     * @return
     */
    public static <T,K> List<T> lGet(K key, long start, long end) {
        try {
            return redisServiceUtil.redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public static <K> long lGetListSize(K key) {
        try {
            return redisServiceUtil.redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引 index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推
     * @return
     */
    public static <T,K> T lGetIndex(K key, long index) {
        try {
            return (T)redisServiceUtil.redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public static <K,V> boolean lSet(K key, V value) {
        try {
            redisServiceUtil.redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public static <K,V> boolean lSet(K key, V value, long time) {
        try {
            redisServiceUtil.redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public static <T,K> boolean lSet(K key, List<T> value) {
        try {
            redisServiceUtil.redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public static <T,K> boolean lSet(K key, List<T> value, long time) {
        try {
            redisServiceUtil.redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public static <T,K> boolean lUpdateIndex(K key, long index, T value) {
        try {
            redisServiceUtil.redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public static <T,K> long lRemove(K key, long count, T value) {
        try {
            Long remove = redisServiceUtil.redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    public static <K,V,R> R getSet(K key,V value){
        R old = get(key);
        set(key,value);
        return old;
    }


    /**
     * @param key key值
     * @param value value值
     * @param expiredTime 过期时间（秒）
     * @return
     */
    public static <K,V> Boolean setExNx(K key, V value, long expiredTime) {
        Boolean resultBoolean = null;
        try {
            resultBoolean = (Boolean) redisServiceUtil.redisTemplate.execute((RedisCallback<Boolean>) connection -> {
                String redisResult = "";
                Object[] obj = serializer(key,value,connection);
                Object nativeConnection = obj[0];
                //lettuce连接包下序列化键值，否知无法用默认的ByteArrayCodec解析
                byte[] keyByte = (byte[])obj[1];
                byte[] valueByte = (byte[])obj[2];
                // lettuce连接包下 redis 单机模式setnx
                if (nativeConnection instanceof RedisAsyncCommands) {
                    RedisAsyncCommands commands = (RedisAsyncCommands) nativeConnection;
                    //同步方法执行、setnx禁止异步
                    redisResult = commands
                            .getStatefulConnection()
                            .sync()
                            .set(keyByte, valueByte, SetArgs.Builder.nx().ex(expiredTime));
                }
                // lettuce连接包下 redis 集群模式setnx
                if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
                    RedisAdvancedClusterAsyncCommands clusterAsyncCommands = (RedisAdvancedClusterAsyncCommands) nativeConnection;
                    redisResult = clusterAsyncCommands
                            .getStatefulConnection()
                            .sync()
                            .set(keyByte, keyByte, SetArgs.Builder.nx().ex(expiredTime));
                }
                //返回加锁结果
                return "OK".equalsIgnoreCase(redisResult);
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultBoolean != null && resultBoolean;
    }

    /**
     * 功能描述: 自定义实现setnx
     * @methodName setNx
     * @author: 刘猛
     * @date: 2019/4/24 17:00
     * @param: [key, value]
     * @return: java.lang.Boolean
     **/
    public static <K,V> Boolean setNx(K key, V value) {
        Boolean resultBoolean = null;
        try {
            resultBoolean = (Boolean) redisServiceUtil.redisTemplate.execute((RedisCallback<Boolean>) connection -> {
                String redisResult = "";
                Object[] obj = serializer(key,value,connection);
                Object nativeConnection = obj[0];
                //lettuce连接包下序列化键值，否知无法用默认的ByteArrayCodec解析
                byte[] keyByte = (byte[])obj[1];
                byte[] valueByte = (byte[])obj[2];
                // lettuce连接包下 redis 单机模式setnx
                if (nativeConnection instanceof RedisAsyncCommands) {
                    RedisAsyncCommands commands = (RedisAsyncCommands) nativeConnection;
                    //同步方法执行、setnx禁止异步
                    redisResult = commands
                            .getStatefulConnection()
                            .sync()
                            .set(keyByte, valueByte, SetArgs.Builder.nx());
                }
                // lettuce连接包下 redis 集群模式setnx
                if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
                    RedisAdvancedClusterAsyncCommands clusterAsyncCommands = (RedisAdvancedClusterAsyncCommands) nativeConnection;
                    redisResult = clusterAsyncCommands
                            .getStatefulConnection()
                            .sync()
                            .set(keyByte, keyByte, SetArgs.Builder.nx());
                }
                //返回加锁结果
                return "OK".equalsIgnoreCase(redisResult);
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultBoolean != null && resultBoolean;
    }

    private static <K,V> Object[] serializer(K key, V value, RedisConnection connection){
        Object[] rs = new Object[3];
        rs[0] = connection.getNativeConnection();
        RedisSerializer<K> keyRdisSerializer = (RedisSerializer<K>) redisServiceUtil.redisTemplate.getKeySerializer();
        RedisSerializer<V> valueRdisSerializer = (RedisSerializer<V>) redisServiceUtil.redisTemplate.getKeySerializer();
        //lettuce连接包下序列化键值，否知无法用默认的ByteArrayCodec解析
        rs[1] = keyRdisSerializer.serialize(key);
        rs[2] = valueRdisSerializer.serialize(value);
        return rs;
    }

}
